package application.manager;

import application.windows.UserInterface;
import application.entity.StageEntity;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

/**
 * 窗口对象管理者
 */
public class ActivityManager {

    /**
     * 窗口对象实体类存放位置
     */
    private volatile Map<Class<?>, StageEntity> stageEntities;

    /**
     * 窗口位置栈对象
     */
    private static PositionStack positionStack;

    /**
     * 窗口位置栈，目前窗口的跳转是以前进栈与后退栈的形式跳转。
     */
    public static class PositionStack {

        /**
         * 窗口后退栈
         * 后退时会从中弹出上一个被压入的窗口组件
         * 当调用UserInterface中的startUserInterface方法时，会将当先窗口压入此栈中。
         */
        private volatile Stack<UserInterface> back;

        /**
         * 窗口前进栈
         * 当调用UserInterface中的end方法时，会将当先窗口压入此栈中
         */
        private volatile Stack<UserInterface> forward;

        /**
         * 本类唯一实例的全局属性
         */
        private static PositionStack positionStack = new PositionStack();

        /**
         * 无参构造方法，当对象生成时，无论是否使用前进与后退栈，都会初始化前进栈与后退栈
         */
        private PositionStack() {
            back = new Stack<>();
            forward = new Stack<>();
        }

        /**
         * 通过此方法，获取PositionStack类的唯一实例
         *
         * @return 本类唯一实例
         */
        public static PositionStack getInstance() {
            if (positionStack == null) {
                positionStack = new PositionStack();
            }
            return positionStack;
        }

        /**
         * 前进栈中是否有窗口等待弹出
         *
         * @return true为有窗口等待弹出，false为没有窗口等待弹出
         */
        public boolean hasForward() {
            return !forward.empty();
        }

        /**
         * 后退栈中是否有窗口等待弹出
         *
         * @return true为有窗口等待弹出，false为没有窗口等待弹出
         */
        public boolean hasBack() {
            return !back.empty();
        }

        /**
         * 通过此方法将当前窗口压入前进栈中，并且从后退栈中弹出一个窗口
         *
         * @param current 当前窗口对象
         * @return 从后退栈中弹出一个窗口作为新窗口对象
         */
        public synchronized UserInterface backOff(UserInterface current) {
            forward.push(current);
            if (back.empty()) {
                return null;
            }
            return back.pop();
        }

        /**
         * 通过此方法，将当前窗口压入后退栈中，并且从前进栈中弹出一个新窗口，如果有的话。
         *
         * @param current 当前窗口对象
         * @return 从前进栈中弹出一个窗口作为新窗口对象
         */
        public synchronized UserInterface forward(UserInterface current) {
            back.push(current);
            if (forward.empty()) {
                return null;
            }
            return forward.pop();
        }
    }

    /**
     * 窗口管理者的唯一全局对象
     */
    private static ActivityManager activityManager = new ActivityManager();

    /**
     * 无参构造方法
     * 生成对象时，会自动初始化窗口对象容器，并且获取PositionStack的唯一对象。
     */
    private ActivityManager() {
        stageEntities = new HashMap<>();
        positionStack = PositionStack.getInstance();
    }

    /**
     * 通过此方法获取窗口管理者的唯一实例
     *
     * @return
     */
    public static ActivityManager getInstance() {
        if (activityManager == null) {
            activityManager = new ActivityManager();
        }
        return activityManager;
    }

    /**
     * 注册一个新窗口
     *
     * @param layout 窗口的布局名称，窗口布局必须卸载resource的layout目录下，否则将找不到窗口布局
     * @return 保存与窗口信息相关的实体对象。
     * @throws IOException 当找不到layout表示的资源文件时，将抛出此异常。
     */
    public StageEntity register(String layout) throws IOException {
        layout = "layout/".concat(layout);
        URL resource = getClass().getClassLoader().getResource(layout);
        FXMLLoader fxmlLoader = new FXMLLoader(resource);
        Pane pane = fxmlLoader.load();
        Object controller = fxmlLoader.getController();
        if (this.stageEntities.containsKey(controller.getClass())) {
            return this.stageEntities.get(controller.getClass());
        }
        StageEntity stageEntity = new StageEntity();
        stageEntity.setPane(pane);
        stageEntity.setController(controller);
        stageEntity.setUiPath(resource);
        Stage stage = new Stage();
        stage.setScene(new Scene(pane));
        stageEntity.setStage(stage);
        this.stageEntities.put(controller.getClass(), stageEntity);
        return stageEntity;
    }

    /**
     * 注册一个新窗口
     *
     * @param layout 窗口的布局名称，窗口布局必须卸载resource的layout目录下，否则将找不到窗口布局
     * @return 保存与窗口信息相关的实体对象。
     * @throws IOException 当找不到layout表示的资源文件时，将抛出此异常。
     */
    public StageEntity register(String layout, Stage stage) throws IOException {
        if (stage == null){
            throw new NullPointerException(Stage.class.getName().concat("Can not Null"));
        }
        layout = "layout/".concat(layout);
        URL resource = getClass().getClassLoader().getResource(layout);
        FXMLLoader fxmlLoader = new FXMLLoader(resource);
        Pane pane = fxmlLoader.load();
        Object controller = fxmlLoader.getController();
        if (this.stageEntities.containsKey(controller.getClass())) {
            return this.stageEntities.get(controller.getClass());
        }
        StageEntity stageEntity = new StageEntity();
        stageEntity.setPane(pane);
        stageEntity.setController(controller);
        stageEntity.setUiPath(resource);
        stage.setScene(new Scene(pane));
        stageEntity.setStage(stage);
        this.stageEntities.put(controller.getClass(), stageEntity);
        return stageEntity;
    }

    /**
     * 获取指定class对应的Controller
     *
     * @param key layout资源文件对应的Controller的类对象
     * @return layout资源文件对应的Controller对象
     */
    public Object getController(Class<?> key) {
        return this.stageEntities.get(key).getController();
    }

    /**
     * 获取指定class对应的布局对象
     *
     * @param key layout资源文件对应的Controller的类对象
     * @return layout映射到Java程序的Pane对象
     */
    public Pane getPane(Class<?> key) {
        return this.stageEntities.get(key).getPane();
    }

    /**
     * 看看是否有指定class对应的窗口信息实体对象
     *
     * @param key layout资源文件对应的Controller的类对象
     * @return true表示有此实体对象，false表示没有此对象
     */
    public boolean isExist(Class<?> key) {
        return this.stageEntities.containsKey(key);
    }

    /**
     * 获取指定class对应的窗口对象
     *
     * @param key layout资源文件对应的Controller的类对象
     * @return 加载layout布局的Stage对象
     */
    public Stage getStage(Class<?> key) {
        return this.stageEntities.get(key).getStage();
    }

    /**
     * 获取PositionStack类的唯一对象
     *
     * @return PositionStack的唯一对象
     */
    public PositionStack getPositionStack() {
        if (positionStack == null) {
            positionStack = PositionStack.getInstance();
        }
        return positionStack;
    }

    /**
     * 调用此方法，注销一个窗口
     *
     * @param cls 窗口controller的类对象
     */
    public void unregister(Class<?> cls) {
        this.stageEntities.remove(cls);
    }
}
